#! /usr/bin/perl -w

## 简单多进程并发控制演示
## 参考 <<多进程 ping 的实现>> 
## huanlf@gmail.com

use strict;
use Net::Ping;
use Net::Netmask;
use Getopt::Std;
use POSIX qw( WNOHANG );

use constant PROCESS_DEFAULT => 20;
use constant TIMEOUT_DEFAULT => 2;
use constant USAGE           => <<"EOF";
Usage:
    $0 [ options ] IP MASK
    $0 [ options ] IP/BITS

    Valid options:
    -p: concurrent process number
    -t: timeout of ping
    -v: verbose

    Example: $0 192.168.1.0 255.255.255.0
    Example: $0 192.168.1.0/24

EOF

my %pid_to_host;    ## concurrency control
my %host_result;    ## ping results
my %opt;            ## command line options

getopts( 'p:t:vh', \%opt ) or die USAGE;

$opt{ p } ||= PROCESS_DEFAULT;
$opt{ t } ||= TIMEOUT_DEFAULT;

my @hosts = @ARGV or die USAGE;
$opt{ h }        and die USAGE;

my $block = new2 Net::Netmask( @hosts ) or
    die "Failed in Net::Netmask-new()";

my $p = Net::Ping->new() or 
    die "Failed in Net::Ping->new()";

for my $ip ($block->enumerate) {

    if ( $ip eq $block->base || $ip eq $block->broadcast ) {
        $host_result{ $ip } = 'SKIP';
        next;
    }

    &wait_for_a_kid if keys %pid_to_host >= $opt{ p };

    my $pid = fork;
    defined( $pid ) or die "Can not fork: $!\n";
    if( $pid ) {
        warn "$pid is processing $ip\n" if $opt{ v };
        $pid_to_host{ $pid } = $ip;
    }
    else {
        exit ( $p->ping( $ip, $opt{ t } ) ? 0 : 1 );
    }
}

for ( sort { $host_result{ $a } cmp $host_result{ $b } } keys %host_result ) {
    printf( "%-17s%4s\n", $_, $host_result{ $_ } );
}

## reap all kids before we exit
1 while &wait_for_a_kid;

## ------------------------------------- ##

sub wait_for_a_kid {
    my $pid = wait;
    return 0 if $pid < 0; # -1

    my $host = delete $pid_to_host{ $pid };
    $host_result{ $host } = ( $? ? "DOWN" : "UP" );
    warn "Reaping $pid...\n" if $opt{ v };

    1;
}
